package com.sharing;

import lombok.extern.slf4j.Slf4j;

/**
 * 临界区
 * 那么在SahreProblem中已经说过了，多个线程下对同一个共享变量同时操作，是会出现一些由于不是原子性导致的问题
 * 一个程序去运行多个线程本身是没有问题的。
 * 问题出在多个线程访问共享资源，多个线程读共享资源其实没有问题，但是多个线程对共享资源读写操作发生指令交错，就会出现问题。
 * 那么，我们会将这类问题称为临界区问题
 * 什么是临界区
 * 定义是这样的：一段代码块内如果存在对共享资源的多线程读写操作，就称这段代码为临界区
 * 而多个线程在临界区内执行，由于代码的执行序列不同而导致结果无法预测，就称之为发生了竞态条件！
 * 那么我们要解决这个问题，可以采用synchorized互斥解决方案,也就是俗称的【对象锁】，也叫对象监视器锁
 * 这种解决方案有两种：
 * 阻塞式解决方案：synchorized、Lock
 * 非阻塞式解决方案：原子变量(底层采用CAS法)
 * TODO 注意
 * 虽然 java 中互斥和同步都可以采用 synchronized 关键字来完成，但它们还是有区别的：
 * 互斥是保证临界区的竞态条件发生，同一时刻只能有一个线程执行临界区代码
 * 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点
 */
@Slf4j
public class CriticalSection {
    //sychronized语法
    /**
     * synchronized(对象) {
     * 临界区
     * }
     */
    static int counter = 0;
    /**
     * 其实这里变量名定位lock更为合适
     * 为什么定为room呢，我们可以这样理解
     * 这个对象可以当成一个房间，而两个线程，线程1和2可以理解为两个人
     * 那么当线程1进入了room房间，就会执行synchronized(room)代码块，
     * 那么这时，线程2也想进，就会发现，room中有人了，就只能阻塞，发生一个上下文切换
     * 那么如果这时，t1没执行完，时间片用完了，那么就会被踢出room，可不要以为，只要进了就会死死拿着不放了
     * 那么t2就会进入room，如果t2执行完了，那么就会退出代码块，这时t1就又能进入了。
     */
    static final Object room = new Object();
    static final Object obj1 = new Object();
    static final Object obj2 = new Object();

    public static void main(String[] args) throws InterruptedException {
        testTwoSynchorizedInnerForCycle();
    }

    /**
     * 两个线程内部都加上对象锁，并且都在for循环内部
     * 每轮循环都会经历一次上下文切换，获取锁释放锁
     * 也就是能让t1和t2轮流执行，并且能保证共享资源的安全
     *
     * @throws InterruptedException
     */
    private static void testTwoSynchorizedInnerForCycle() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {
                    counter++;
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {
                    counter--;
                }
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}", counter);
    }

    /**
     * 放到for循环外边
     * 这是保证了每个线程执行的原子性
     * 当一个线程的for循环全部执行完，才会去做另一个线程的
     *
     * @throws InterruptedException
     */
    private static void testTwoSynchorizedOutForCycle() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            synchronized (room) {
                for (int i = 0; i < 5000; i++) {
                    counter++;
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            synchronized (room) {
                for (int i = 0; i < 5000; i++) {
                    counter--;
                }
            }

        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}", counter);
    }

    /**
     * t1和t2分别锁不同的对象，obj1和obj2
     * 那么，这种情况下，t1和t2不会相互阻塞，因为它们持有不同的锁
     *
     * @throws InterruptedException
     */
    private static void testTwoDiffSynchorizedInnerForCycle() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (obj1) {
                    counter++;
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (obj2) {
                    counter--;
                }
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}", counter);
    }

    /**
     * t1和t2一个线程加锁一个线程不加锁
     * 这种情况下，t1只有在获取到锁之后才能对counter进行操作
     * 而t2随时随地可对counter进行操作，是线程不安全的
     *
     * @throws InterruptedException
     */
    private static void testOneSynchorizedInnerForCycle() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (obj1) {
                    counter++;
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (obj2) {
                    counter--;
                }
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}", counter);
    }


}
